/* 
1. 组件之外可共享状态/数据
  闭包
2. 有明确修改数据的方法，可通知到指定的位置
  a. 消息订阅
  b. proxy
3. 数据变化可触发 UI 重新渲染
  a. forceUpdate   setState(Math.random())
  b. react - useSyncExternalStore
*/

import { useEffect, useState, useRef } from 'react';
import useExternalStoreWithSelectorExports from 'use-sync-external-store/shim/with-selector';
import isEqual from 'fast-deep-equal';
// import { isEqual } from 'lodash';
// import { create as createFromZustand } from 'zustand';
// import { useShallow as useShallowFromZustand } from 'zustand/react/shallow';

const { useSyncExternalStoreWithSelector } = useExternalStoreWithSelectorExports;

function createStore(createState) {
  let state;
  let listeners = new Set();

  function getState() {
    return state;
  }

  function setState(partial, isReplace) {
    const newState = typeof partial === 'function' ? partial(state) : partial;
    
    if (Object.is(state, newState)) return;

    const previousState = state;

    if (!isReplace) {
      // 新旧数据合并
      state = (typeof newState !== 'object' || newState === null) ? newState : { ...state, ...newState }
    } else {
      // 覆盖更新
      state = newState;
    }

    listeners.forEach(listener => listener(state, previousState));
  }

  function subscribe(listener) {
    listeners.add(listener);

    return () => listeners.delete(listener);
  }

  function destory() {
    listeners.clear();
  }

  // 可以外部调用的方法
  const api = { getState, setState, subscribe, destory };

  // 设置初始值
  state = createState(setState, getState, api);

  return api;
}

function useStore1(api, selector) {
  const [, setRandom] = useState();

  useEffect(() => {
    api.subscribe((state, previousState) => {
      const newObj = selector(state);
      const oldObj = selector(previousState);

      if (newObj !== oldObj) {
        setRandom(Math.random());
      }
    });
  }, []);

  return selector(api.getState());
}

function useStore2(api, selector) {
  const _isEqual = (a, b) => {
    // return Object.is(a, b);
    console.log('isEqual(a, b) ---> ', isEqual(a, b));
    return isEqual(a, b);
  };

  return useSyncExternalStoreWithSelector(
    api.subscribe,
    api.getState,
    api.getState,
    selector,
    isEqual
  );
}

function create(createState) {
  const api = createStore(createState);
  const useBoundStore = selector => useStore2(api, selector);

  // 给 useBoundStore 赋值 api 的方法，可以在组件外部调用
  Object.assign(useBoundStore, api);

  return useBoundStore;
}

function useShallow(selector) {
  const prev = useRef();

  return (state) => {
    const next = selector(state);
    return isEqual(next, prev.current) ? prev.current : (prev.current = next);
  };
}

// ------------ 使用 -------------

const logMiddleware = (fn) => {
  return function (set, get, store) {
    function newSet(previousState) {
      console.log('previousState ---> ', get());
      return set(previousState);
    }
    
    return fn(newSet, get, store);
  }
};

const useXxxStore = create(logMiddleware((set) => ({
  aaa: 'aaaaaaa',
  bbb: '',
  obj: { a: 1 },
  setObj: value => set({ obj: value }),
  updateAaa: (value) => set({ aaa: value }),
  updateBbb: value => set({ bbb: value })
})));

// console.log(useXxxStore.getState());

const useXxxStoreWithShallow = selector => useXxxStore(selector);
// const useXxxStoreWithShallow = selector => useXxxStore(useShallow(selector));

function Test() {
  const { obj, setObj } = useXxxStoreWithShallow(state => ({ obj: state.obj, setObj: state.setObj }));

  console.log('render');

  return <div>
    <div>{JSON.stringify(obj)}</div>
    <button onClick={() => {
      setObj({ a: 1 });
    }}>改变</button>
  </div>
}

function App() {
  const { aaa, updateAaa } = useXxxStoreWithShallow(state => ({
    aaa: state.aaa,
    updateAaa: state.updateAaa
  }));

  const onChange = (event) => {
    updateAaa(event.target.value);
  };

  return (
    <div className="App">
      <input onChange={onChange}></input>
      hello, {aaa}
      <Test />
    </div>
  );
}

export default App;
